Skip to content

refactor: split out blog route from shared MdxRoute file#1213

Merged
jderochervlk merged 26 commits intomasterfrom
vlk/split-out-blog-route
Apr 3, 2026
Merged

refactor: split out blog route from shared MdxRoute file#1213
jderochervlk merged 26 commits intomasterfrom
vlk/split-out-blog-route

Conversation

@jderochervlk
Copy link
Copy Markdown
Collaborator

@jderochervlk jderochervlk commented Mar 20, 2026

Summary

Splits blog article rendering out of the monolithic MdxRoute into a dedicated BlogArticleRoute, and introduces reusable infrastructure (MdxFile, MdxContent, CompiledMdx) that paves the way for splitting out the remaining routes (docs, community, syntax-lookup) and eventually removing react-router-mdx.

Motivation

MdxRoute.res handles every MDX-backed page on the site — blog posts, docs, community pages, and syntax lookup — in a single loader and a long chain of if/else branches. This makes it hard to reason about, test, and modify individual sections without risk of breaking others. Blog is the simplest case and a good first candidate to extract.

What changed

New files

File Purpose
src/common/CompiledMdx.res / .resi Opaque type wrapping a compiled MDX string. Prevents consumers from treating it as a raw string.
src/MdxFile.res / .resi Server-side utilities: resolve URL → file path, read + parse frontmatter, scan directories for .mdx files, and compile MDX via @mdx-js/mdx.
src/components/MdxContent.res / .resi <MdxContent compiledMdx /> React component. Evaluates compiled MDX using runSync from @mdx-js/mdx with proper ReScript bindings to react/jsx-runtime (no %raw). Contains the shared component map used across all MDX pages.
app/routes/BlogArticleRoute.res / .resi Dedicated route for individual blog posts.
src/common/MarkdownParser.res Added stripFrontmatterPlugin to remove YAML nodes from the remark tree so frontmatter doesn't appear in rendered output.

Modified files

File Change
app/routes.res Blog routes now point to BlogArticleRoute.jsx; mdxRoutes filters out blog/* paths to avoid duplicates.
app/routes/MdxRoute.res / .resi Removed blogPost? field from loaderData and the blog-specific branch in both the loader and renderer.
src/bindings/Rehype.res Removed unused rehype-raw binding.
package.json / yarn.lock Added rehype-raw (used by other parts of the site, not by this route).

How MDX rendering works

The blog route uses the same MDX pipeline as react-router-mdx but with our own code, which will make it possible to remove that dependency later:

Server (loader):

Raw .mdx file on disk
  → Node.Fs.readFile
  → MdxFile.compileMdx (calls @mdx-js/mdx compile with outputFormat: "function-body")

Client (component):

CompiledMdx.t (opaque compiled string from loader data)
  → MdxContent evaluates with runSync + react/jsx-runtime
  → Renders with shared component map (CodeTab, Info, Warn, etc.)

This means custom MDX components like <CodeTab labels={["RE", "JS"]}> work correctly — unlike the earlier approach of passing raw markdown to react-markdown, which can't handle JSX expressions in attributes.

Key design decisions

  • CompiledMdx.t is opaque — the .resi exposes only type t and fromCompileResult. You can't accidentally pass a random string where compiled MDX is expected.
  • MdxContent owns the component map — since the same components are used across all MDX pages on the site, they live in one place rather than being duplicated in each route.
  • No %rawMdxContent uses proper @module bindings to react/jsx-runtime and @mdx-js/mdx instead of raw JS. The jsx-runtime exports are imported as opaque jsxRuntimeValue types to sidestep ReScript's monomorphisation of the polymorphic React.jsx/React.jsxs signatures.
  • MdxFile.compileMdx calls @mdx-js/mdx compile directly, making react-router-mdx no longer needed for blog routes. The remaining routes still use it for now.

@jderochervlk jderochervlk marked this pull request as ready for review March 20, 2026 19:14
@jderochervlk jderochervlk marked this pull request as draft March 20, 2026 19:14
jderochervlk added a commit that referenced this pull request Apr 2, 2026
- Use String.startsWith instead of String.includes for blog route
  filtering to avoid accidentally excluding non-blog routes that
  contain 'blog' as a substring

- Replace JsExn.throw with JsError.throwWithMessage in
  BlogArticleRoute for consistency with BlogApi.res and to get
  proper Error objects with stack traces

- Normalize path separators in MdxFile.scanDir to fix Windows
  compatibility where Node.Path.join2 produces backslashes
- Use String.startsWith instead of String.includes for blog route
  filtering to avoid accidentally excluding non-blog routes that
  contain 'blog' as a substring

- Replace JsExn.throw with JsError.throwWithMessage in
  BlogArticleRoute for consistency with BlogApi.res and to get
  proper Error objects with stack traces

- Normalize path separators in MdxFile.scanDir to fix Windows
  compatibility where Node.Path.join2 produces backslashes
@jderochervlk jderochervlk requested a review from Copilot April 2, 2026 15:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors routing so blog article pages are handled by a dedicated route instead of being a special case inside the shared MdxRoute.

Changes:

  • Added BlogArticleRoute and updated route registration to generate per-post blog article routes from the filesystem.
  • Introduced MdxFile utilities and updated MarkdownParser to strip YAML frontmatter from rendered content.
  • Added rehype-raw dependency/binding and updated Vitest/browser config plus refreshed screenshot artifacts.

Reviewed changes

Copilot reviewed 12 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
yarn.lock Locks new transitive deps for rehype-raw and related hast/parse5 utilities.
vitest.config.mjs Minor config formatting/line adjustment around Playwright provider.
src/MdxFile.resi Public interface for resolving/loading/scanning .mdx files.
src/MdxFile.res Implements disk path resolution, file loading, and recursive .mdx scanning for route generation.
src/common/MarkdownParser.res Adds a unified plugin to strip YAML frontmatter nodes from the output content.
src/bindings/Rehype.res Adds ReScript binding for rehype-raw.
package.json Adds rehype-raw dependency.
app/routes/MdxRoute.resi Removes blogPost from MdxRoute loader data type.
app/routes/MdxRoute.res Removes blog-specific loader/rendering branches (blog handled elsewhere now).
app/routes/BlogArticleRoute.resi New route interface for blog articles.
app/routes/BlogArticleRoute.res New loader/view for individual blog posts using filesystem reads + ReactMarkdown.
app/routes.res Registers generated blog article routes and filters blog paths out of generic MDX routes.
AGENTS.md Adds additional ReScript guidance for contributors.
tests/screenshots/Button_.test.jsx/button-secondary-red-chromium-linux.png Updated screenshot artifact.
tests/screenshots/Button_.test.jsx/button-primary-blue-chromium-linux.png Updated screenshot artifact.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Updates the Markdown image caption test to use a new caption and image.
Regenerates all related test screenshots to reflect the change.
Add detailed instructions for running and updating Vitest
browser-based unit tests in the README. Remove outdated
screenshot baseline PNGs from __tests__/__screenshots__.
Add guidance in README to be selective when updating screenshots.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

Cloudflare deployment

Deployement ID: 81ccc9d8-e17c-42f0-b27f-b7588b741313
Deployment Environment: preview

⛅️ wrangler 4.63.0 (update available 4.80.0)
─────────────────────────────────────────────
✨ Compiled Worker successfully
Uploading... (7653/7655)
Uploading... (7654/7655)
Uploading... (7655/7655)
✨ Success! Uploaded 2 files (7653 already uploaded) (1.85 sec)

✨ Uploading _redirects
✨ Uploading Functions bundle
🌎 Deploying...
✨ Deployment complete! Take a peek over at https://81ccc9d8.rescript-lang.pages.dev
✨ Deployment alias URL: https://vlk-split-out-blog-route.rescript-lang.pages.dev

Base automatically changed from vlk/disable-animations to master April 3, 2026 21:25
@jderochervlk jderochervlk marked this pull request as ready for review April 3, 2026 21:25
@jderochervlk jderochervlk merged commit a3ed0b8 into master Apr 3, 2026
5 checks passed
@jderochervlk jderochervlk deleted the vlk/split-out-blog-route branch April 3, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants